package ua.edu.nuos.jeetraining2012.cms.ui.views;

import com.vaadin.data.Property;
import com.vaadin.data.fieldgroup.BeanFieldGroup;
import com.vaadin.data.fieldgroup.FieldGroup;
import com.vaadin.data.util.BeanItemContainer;
import com.vaadin.event.Action;
import com.vaadin.navigator.Navigator;
import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewChangeListener;
import com.vaadin.server.ExternalResource;
import com.vaadin.shared.ui.MarginInfo;
import com.vaadin.ui.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vaadin.openesignforms.ckeditor.CKEditorConfig;
import org.vaadin.openesignforms.ckeditor.CKEditorTextField;
import ua.edu.nuos.jeetraining2012.cms.entity.CourseEntity;
import ua.edu.nuos.jeetraining2012.cms.entity.CourseMaterialEntity;
import ua.edu.nuos.jeetraining2012.cms.entity.HomeWorkEntity;
import ua.edu.nuos.jeetraining2012.cms.entity.UsersToCourseEntity;
import ua.edu.nuos.jeetraining2012.cms.service.Course;
import ua.edu.nuos.jeetraining2012.cms.service.HomeWork;
import ua.edu.nuos.jeetraining2012.cms.service.Material;
import ua.edu.nuos.jeetraining2012.cms.service.exception.ServiceException;
import ua.edu.nuos.jeetraining2012.cms.service.exception.ServiceValidationException;
import ua.edu.nuos.jeetraining2012.cms.ui.components.HeaderPanel;
import ua.edu.nuos.jeetraining2012.cms.util.LocaleWrapper;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.predefined.StandardGoogleApiVariables;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.service.exception.auth.GoogleAuthException;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.service.exception.drive.GDriveFilePermissionException;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.service.exception.drive.GDriveUploadException;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.service.exception.transport.GoogleTransportException;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.service.exception.youtube.YouTubeUploadException;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.services.drive.GoogleDriveUploader;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.services.youtube.YouTubeUploader;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.*;

public class CoursesView extends VerticalLayout implements View {
    private static final Action actionAddMaterial, actionAddHomeWork, actionRemove, actionEnrollApprove, actionEnrollBlock;
    private static final Action[] menuMaterial, menuHomework;
    final private Logger logger = LoggerFactory.getLogger(getClass());
    private HeaderPanel headerPanel;
    private Course course;
    private Material material;
    private HomeWork homework;
    private Tree tree;
    private ResourceBundle bundle = LocaleWrapper.getBundle("admin");
    private CourseTabsheetLayout courseTabsheetLayout = new CourseTabsheetLayout();
    private MaterialTabsheetLayout materialTabsheetLayout = new MaterialTabsheetLayout();
    private HomeWorkTabsheetLayout homeWorkTabsheetLayout = new HomeWorkTabsheetLayout();
    private HorizontalSplitPanel hsplit = new HorizontalSplitPanel();
    private Button addCourseEntryBtn, addMaterialHWEntryBtn, removeEntryBtn;
    private VerticalLayout emptyLayout = new VerticalLayout();
    private Button.ClickListener addBtnListener = new AddBtnListener();

    /**
     * Constructs view object and add components to the View
     *
     * @param navigator
     */
    public CoursesView(Navigator navigator) {
        headerPanel = new HeaderPanel(navigator, "");
        try {
            InitialContext ic = new InitialContext();
            course = (Course) ic.lookup("java:module/Course");
            material = (Material) ic.lookup("java:module/Material");
            homework = (HomeWork) ic.lookup("java:module/HomeWork");
        } catch (NamingException e) {
            logger.error("Course EJB lookup failed", e);
        }
        addCourseEntryBtn = new Button(bundle.getString("addCourse"));
        addMaterialHWEntryBtn = new Button(bundle.getString("addMaterial"));
        removeEntryBtn = new Button(bundle.getString("remove"));
        addCourseEntryBtn.addClickListener(addBtnListener);
        addCourseEntryBtn.setImmediate(true);
        addMaterialHWEntryBtn.addClickListener(addBtnListener);
        addMaterialHWEntryBtn.setImmediate(true);
        removeEntryBtn.addClickListener(new RemoveBtnListener());
        removeEntryBtn.setImmediate(true);
        VerticalLayout vLayoutContainer = new VerticalLayout();
        HorizontalLayout btnMgmtLayout = new HorizontalLayout();
        btnMgmtLayout.addComponent(addCourseEntryBtn);
        btnMgmtLayout.addComponent(addMaterialHWEntryBtn);
        btnMgmtLayout.addComponent(removeEntryBtn);

        TreeListener treeListener = new TreeListener();
        tree = new Tree();
        tree.setItemCaptionMode(AbstractSelect.ItemCaptionMode.EXPLICIT);
        tree.addValueChangeListener(treeListener);
        tree.addActionHandler(treeListener);
        tree.setImmediate(true);
        tree.setHeight(100, Unit.PERCENTAGE);
        tree.setItemStyleGenerator(new Tree.ItemStyleGenerator() {
            @Override
            public String getStyle(Tree source, Object itemId) {
                Collection clctn = source.getChildren(itemId);
                if ((clctn == null) || (clctn.size() == 0)) {
                    return "hide-arrow";
                }
                return null;
            }
        });

        vLayoutContainer.addComponent(btnMgmtLayout);
        vLayoutContainer.addComponent(tree);

        hsplit.setHeight(100, Unit.PERCENTAGE);
        hsplit.setSplitPosition(25, Unit.PERCENTAGE);
        hsplit.setFirstComponent(vLayoutContainer);
        hsplit.setSecondComponent(emptyLayout);

        this.addComponent(headerPanel.getHeaderLayout());
        this.addComponent(hsplit);
        this.setHeight(100, Unit.PERCENTAGE);
        this.setExpandRatio(hsplit, 0.97f);
    }

    static {
        ResourceBundle bundle = LocaleWrapper.getBundle("admin");
        actionAddMaterial = new Action(bundle.getString("addMaterial"));
        actionAddHomeWork = new Action(bundle.getString("addHomework"));
        actionRemove = new Action(bundle.getString("remove"));
        actionEnrollApprove = new Action(bundle.getString("approveEnrollment"));
        actionEnrollBlock = new Action(bundle.getString("blockEnrollment"));
        menuMaterial = new Action[]{actionAddMaterial, actionRemove};
        menuHomework = new Action[]{actionAddHomeWork, actionRemove};
    }

    @Override
    public void enter(ViewChangeListener.ViewChangeEvent event) {
        hsplit.setSecondComponent(emptyLayout);
        addMaterialHWEntryBtn.setEnabled(false);
        resetTreeContent();
    }

    /**
     * Flush Tree content and fill it with course/materials
     */
    private void resetTreeContent() {
        tree.removeAllItems();
        try {
            List<CourseEntity> courses = course.getAvailableCourses();
            for (CourseEntity entity : courses) {
                tree.addItem(entity);
                tree.setItemCaption(entity, entity.getTitle());
                addTreeCourseMaterial(entity);
            }
        } catch (ServiceException e) {
            logger.error("Course exception", e);
        }
    }

    /**
     * Adds material as child nodes to course node.
     * Course object must be part of a Tree already.
     *
     * @param entity Course object
     * @throws IllegalAccessException Course object is null or it's not a part of a Tree component
     */
    private void addTreeCourseMaterial(CourseEntity entity) {
        if (entity == null)
            throw new IllegalArgumentException("Entity can't be null");
        if (tree.getItem(entity) == null)
            throw new IllegalArgumentException("Entity object must be child in a tree");
        try {
            List<CourseMaterialEntity> materials = material.getAllMaterials(entity.getId());
            List<String> sectionTitles = material.getSectionTitles(entity.getId());
            for (String sTitle : sectionTitles) {
                tree.addItem(sTitle);
                tree.setParent(sTitle, entity);
                tree.setItemCaption(sTitle, sTitle);
            }

            for (CourseMaterialEntity material : materials) {
                tree.addItem(material);
                tree.setParent(material, material.getSectionTitle());
                tree.setItemCaption(material, material.getTitle());
                addTreeHomework(material);
            }
        } catch (ServiceException e) {
            logger.error("Material exception", e);
        }

    }

    /**
     * Adds homework as child nodes to material node.
     * Material object must be part of a Tree already.
     *
     * @param entity CourseMaterialEntity object
     * @throws IllegalAccessException Course object is null or it's not a part of a Tree component
     */
    private void addTreeHomework(CourseMaterialEntity entity) {
        if (entity == null)
            throw new IllegalArgumentException("Entity can't be null");
        if (tree.getItem(entity) == null)
            throw new IllegalArgumentException("Entity object must be child in a tree");

        List<HomeWorkEntity> homeworkList = homework.getHomeworkByMaterialId(entity.getId());
        for (HomeWorkEntity homeworkEntity : homeworkList) {
            tree.addItem(homeworkEntity);
            tree.setParent(homeworkEntity, entity);
            tree.setItemCaption(homeworkEntity, homeworkEntity.getTitle());
        }
    }

    /**
     * Constructs and returns CKEditorTextField object
     *
     * @return configured CKEditorTextField object
     */
    private CKEditorTextField getWYSIWYGEditor() {
        CKEditorConfig ckEditorConfig = new CKEditorConfig();
        ckEditorConfig.useCompactTags();
        ckEditorConfig.disableElementsPath();
        ckEditorConfig.setHeight("330px");
        ckEditorConfig.setWidth("100%");
        ckEditorConfig.setFullPage(false);
        CKEditorTextField editor = new CKEditorTextField(ckEditorConfig);
        editor.setSizeFull();
        editor.setImmediate(true);
        return editor;
    }

    /**
     * Class that implements value change for a Tree component
     */
    private class TreeListener implements Property.ValueChangeListener, Action.Handler {
        @Override
        public void valueChange(Property.ValueChangeEvent event) {
            Object object = event.getProperty().getValue();
            if (object instanceof CourseEntity) {
                courseTabsheetLayout.setEntity((CourseEntity) object);
                hsplit.setSecondComponent(courseTabsheetLayout.containerLayout);
                courseTabsheetLayout.tabSheet.setSelectedTab(0);
                addMaterialHWEntryBtn.setEnabled(true);
                addMaterialHWEntryBtn.setCaption(bundle.getString("addMaterial"));
                removeEntryBtn.setEnabled(true);
            } else if (object instanceof CourseMaterialEntity) {
                materialTabsheetLayout.setEntity((CourseMaterialEntity) object);
                hsplit.setSecondComponent(materialTabsheetLayout.containerLayout);
                materialTabsheetLayout.tabSheet.setSelectedTab(0);
                addMaterialHWEntryBtn.setEnabled(true);
                addMaterialHWEntryBtn.setCaption(bundle.getString("addHomework"));
                removeEntryBtn.setEnabled(true);
            } else if (object instanceof HomeWorkEntity) {
                homeWorkTabsheetLayout.setEntity((HomeWorkEntity) object);
                hsplit.setSecondComponent(homeWorkTabsheetLayout.containerLayout);
                homeWorkTabsheetLayout.tabSheet.setSelectedTab(0);
                addMaterialHWEntryBtn.setEnabled(false);
                removeEntryBtn.setEnabled(true);
            } else {
                hsplit.setSecondComponent(emptyLayout);
                removeEntryBtn.setEnabled(false);
                addMaterialHWEntryBtn.setEnabled(false);
            }
        }

        @Override
        public Action[] getActions(Object target, Object sender) {
            if (target instanceof CourseEntity) {
                return menuMaterial;
            } else if (target instanceof CourseMaterialEntity) {
                return menuHomework;
            } else if (target instanceof HomeWorkEntity) {
                return new Action[]{actionRemove};
            }
            return new Action[0];
        }

        @Override
        public void handleAction(Action action, Object sender, Object target) {
            try {
                if (action == actionRemove) {
                    if (target instanceof CourseEntity) {
                        course.delete((CourseEntity) target);
                    } else if (target instanceof CourseMaterialEntity) {
                        material.delete((CourseMaterialEntity) target);
                    } else if (target instanceof HomeWorkEntity) {
                        homework.delete((HomeWorkEntity) target);
                    }
                    hsplit.setSecondComponent(emptyLayout);
                    resetTreeContent();
                } else if ((action == actionAddMaterial) &&
                        (target instanceof CourseEntity)) {
                    CourseMaterialEntity entity = material.getEmptyEntity();
                    entity.setCourse((CourseEntity) target);
                    materialTabsheetLayout.setEntity(entity);
                    hsplit.setSecondComponent(materialTabsheetLayout.containerLayout);
                    materialTabsheetLayout.tabSheet.setSelectedTab(0);
                } else if ((action == actionAddHomeWork) &&
                        (target instanceof CourseMaterialEntity)) {
                    HomeWorkEntity entity = homework.getEmptyEntity();
                    entity.setMaterial((CourseMaterialEntity) target);
                    homeWorkTabsheetLayout.setEntity(entity);
                    hsplit.setSecondComponent(homeWorkTabsheetLayout.containerLayout);
                    homeWorkTabsheetLayout.tabSheet.setSelectedTab(0);
                }
            } catch (ServiceException | ServiceValidationException e) {
                logger.error("Course exception", e);
            }
        }
    }

    /**
     * Class that implements ClickListener for add button
     */
    private class AddBtnListener implements Button.ClickListener {
        @Override
        public void buttonClick(Button.ClickEvent event) {
            Object obj = tree.getValue();
            if (event.getButton() == addCourseEntryBtn) {
                courseTabsheetLayout.setEntity(course.getEmptyEntity());
                hsplit.setSecondComponent(courseTabsheetLayout.containerLayout);
                courseTabsheetLayout.tabSheet.setSelectedTab(0);
            } else if ((event.getButton() == addMaterialHWEntryBtn) &&
                    (obj instanceof CourseEntity)) {
                CourseMaterialEntity entity = material.getEmptyEntity();
                entity.setCourse((CourseEntity) obj);
                materialTabsheetLayout.setEntity(entity);
                hsplit.setSecondComponent(materialTabsheetLayout.containerLayout);
                materialTabsheetLayout.tabSheet.setSelectedTab(0);
            } else if ((event.getButton() == addMaterialHWEntryBtn) &&
                    (obj instanceof CourseMaterialEntity)) {
                HomeWorkEntity entity = homework.getEmptyEntity();
                entity.setMaterial((CourseMaterialEntity) obj);
                homeWorkTabsheetLayout.setEntity(entity);
                hsplit.setSecondComponent(homeWorkTabsheetLayout.containerLayout);
                homeWorkTabsheetLayout.tabSheet.setSelectedTab(0);
            }
        }
    }

    /**
     * Class that implements ClickListener for remove button
     */
    private class RemoveBtnListener implements Button.ClickListener {
        @Override
        public void buttonClick(Button.ClickEvent event) {
            try {
                Object obj = tree.getValue();
                if (obj instanceof CourseEntity) {
                    course.delete((CourseEntity) obj);
                } else if (obj instanceof CourseMaterialEntity) {
                    material.delete((CourseMaterialEntity) obj);
                }
                hsplit.setSecondComponent(emptyLayout);
                resetTreeContent();
            } catch (Exception e) {
                logger.error("Course exception", e);
            }
        }
    }

    /**
     * Class that implements Course Tabsheet Layout
     */
    private class CourseTabsheetLayout {
        private final BeanFieldGroup binder;
        private TabSheet tabSheet;
        private FormLayout formGeneral, formShortDescription, formDescription, formUsers;
        private SubmitBtnListener submitListener = new SubmitBtnListener();
        private VerticalLayout containerLayout = new VerticalLayout();
        private Table table;
        private Set<UsersToCourseEntity> enrolledTableSelection;

        private CourseTabsheetLayout() {
            tabSheet = new TabSheet();
            tabSheet.setHeight(100, Unit.PERCENTAGE);
            tabSheet.setImmediate(true);
            formGeneral = new FormLayout();

            binder = new BeanFieldGroup(CourseEntity.class);
            TextField title = new TextField(bundle.getString("title"));
            title.setSizeFull();
            DateField startDate = new DateField(bundle.getString("start_date"));
            DateField finishDate = new DateField(bundle.getString("finish_date"));

            formGeneral.addComponent(title);
            HorizontalLayout hLayout = new HorizontalLayout();
            hLayout.addComponent(startDate);
            hLayout.addComponent(finishDate);
            formGeneral.addComponent(hLayout);
            formGeneral.setMargin(new MarginInfo(false, true, true, true));
            formGeneral.addComponent(getSubmitButton());
            formGeneral.setImmediate(true);
            tabSheet.addTab(formGeneral, bundle.getString("tab_general"));

            formShortDescription = new FormLayout();
            formShortDescription.setMargin(new MarginInfo(false, true, true, true));
            formShortDescription.setImmediate(true);
            CKEditorTextField shortDescription = getWYSIWYGEditor();
            formShortDescription.addComponent(shortDescription);
            formShortDescription.addComponent(getSubmitButton());
            tabSheet.addTab(formShortDescription, bundle.getString("tab_shortdescription"));

            CKEditorTextField description = getWYSIWYGEditor();

            formDescription = new FormLayout();
            formDescription.addComponent(description);
            formDescription.addComponent(getSubmitButton());
            formDescription.setImmediate(true);
            formDescription.setMargin(new MarginInfo(false, true, true, true));
            tabSheet.addTab(formDescription, bundle.getString("tab_fulldescription"));

            BeanItemContainer tableContainer = new BeanItemContainer(UsersToCourseEntity.class);
            tableContainer.addNestedContainerBean("courseId");
            tableContainer.addNestedContainerBean("userId");

            EnrollTableListener enrollTableListener = new EnrollTableListener();
            formUsers = new FormLayout();
            table = new Table();
            table.setContainerDataSource(tableContainer);
            table.setSelectable(true);
            table.setMultiSelect(true);
            table.setImmediate(true);
            table.setVisibleColumns(new Object[]{"userId.email", "userId.name", "approveDate", "joinDate", "status"});
            table.addActionHandler(enrollTableListener);
            table.addValueChangeListener(enrollTableListener);
            formUsers.addComponent(table);
            tabSheet.addTab(formUsers, bundle.getString("tab_student"));

            binder.bind(title, "title");
            binder.bind(startDate, "startDate");
            binder.bind(finishDate, "finishDate");
            binder.bind(shortDescription, "shortDescription");
            binder.bind(description, "description");

            containerLayout.setSizeFull();
            containerLayout.addComponent(tabSheet);
            containerLayout.setImmediate(true);
        }

        private Button getSubmitButton() {
            Button btn = new Button(bundle.getString("save"));
            btn.addClickListener(submitListener);
            return btn;
        }

        private void setEntity(CourseEntity entity) {
            if (entity == null) {
                logger.info("setEntity() entity object is null");
                throw new IllegalArgumentException("Entity object can't be null");
            }
            populateEnrolledTableWithStudents(entity.getId());
            binder.setItemDataSource(entity);
        }

        private void populateEnrolledTableWithStudents(long courseId) {
            if (courseId > 0) {
                List<UsersToCourseEntity> students = course.getCourseStudents(courseId);
                table.removeAllItems();
                for (UsersToCourseEntity student : students) {
                    table.addItem(student);
                }
            }
        }

        /**
         * Class that implements ClickListener for submit button
         */
        private class SubmitBtnListener implements Button.ClickListener {
            @Override
            public void buttonClick(Button.ClickEvent event) {
                try {
                    binder.commit();
                    course.persist((CourseEntity) binder.getItemDataSource().getBean());
                    Notification.show(bundle.getString("saved"), Notification.Type.TRAY_NOTIFICATION);
                    hsplit.setSecondComponent(emptyLayout);
                    resetTreeContent();
                } catch (FieldGroup.CommitException e) {
                    logger.error("BeanBinder commit exception", e);
                } catch (ServiceValidationException | ServiceException e) {
                    logger.error("Can't persist material", e);
                }
            }
        }

        private class EnrollTableListener implements Action.Handler, Property.ValueChangeListener {

            @Override
            public Action[] getActions(Object target, Object sender) {
                if (target != null) {
                    return new Action[]{actionEnrollApprove, actionEnrollBlock};
                }
                return new Action[0];  //To change body of implemented methods use File | Settings | File Templates.
            }

            @Override
            public void handleAction(Action action, Object sender, Object target) {
                if (enrolledTableSelection != null) {
                    for (UsersToCourseEntity student : enrolledTableSelection) {
                        try {
                            String md5 = course.getStudentToCourseApproveMd5(student.getUserId().getId(), student.getCourseId().getId());
                            if (action == actionEnrollBlock) {
                                course.discardStudentEnrollment(md5, "");
                            } else if (action == actionEnrollApprove) {
                                course.approveStudentEnrollment(md5, "");
                            }
                        } catch (ServiceException e) {
                            logger.error("handleAction() enrollBlock ", e.getMessage());
                        }
                    }
                    CourseEntity entity = (CourseEntity) binder.getItemDataSource().getBean();
                    if ((entity != null) && (entity.getId() > 0)) {
                        populateEnrolledTableWithStudents(entity.getId());
                    }
                }
            }

            @Override
            public void valueChange(Property.ValueChangeEvent event) {
                enrolledTableSelection = (Set<UsersToCourseEntity>) event.getProperty().getValue();
            }
        }
    }

    /**
     * Class that implements Material Tabsheet Layout
     */
    private class MaterialTabsheetLayout {
        private final BeanFieldGroup binder;
        private ComboBox sectionTitleBox;
        private TabSheet tabSheet;
        private FormLayout formGeneral, formShortDescription, formDescription;
        private SubmitBtnListener submitListener = new SubmitBtnListener();
        private VerticalLayout containerLayout = new VerticalLayout();
        private UploadListener uploadLsn = new UploadListener();
        private Button videoClearBtn, slideClearBtn;
        private Link videoURL, slideURL;

        private MaterialTabsheetLayout() {
            tabSheet = new TabSheet();
            tabSheet.setHeight(100, Unit.PERCENTAGE);
            tabSheet.setImmediate(true);
            formGeneral = new FormLayout();

            binder = new BeanFieldGroup(CourseMaterialEntity.class);
            TextField title = new TextField(bundle.getString("title"));
            title.setSizeFull();
            DateField startDate = new DateField(bundle.getString("start_date"));
            DateField finishDate = new DateField(bundle.getString("due_date"));

            //Upload file
            Upload uploadFile = new Upload(bundle.getString("uploadfile"), uploadLsn);
            uploadFile.setButtonCaption("Upload");
            uploadFile.addSucceededListener(uploadLsn);
            uploadFile.addFailedListener(uploadLsn);

            sectionTitleBox = new ComboBox(bundle.getString("sectionTitle"));
            sectionTitleBox.setTextInputAllowed(true);
            sectionTitleBox.setNewItemsAllowed(true);
            sectionTitleBox.setImmediate(true);
            sectionTitleBox.setNullSelectionAllowed(false);
            sectionTitleBox.setNewItemHandler(new AbstractSelect.NewItemHandler() {
                @Override
                public void addNewItem(String s) {
                    for (final Object itemId : sectionTitleBox.getItemIds()) {
                        if (s.equalsIgnoreCase(itemId.toString())) {
                            return;
                        }
                    }
                    sectionTitleBox.addItem(s);
                    sectionTitleBox.select(s);
                }
            });

            formGeneral.addComponent(title);
            HorizontalLayout hLayout = new HorizontalLayout();
            hLayout.addComponent(startDate);
            hLayout.addComponent(finishDate);
            formGeneral.addComponent(sectionTitleBox);
            formGeneral.addComponent(hLayout);
            formGeneral.setMargin(new MarginInfo(false, true, true, true));
            videoURL = new Link();
            slideURL = new Link();
            videoClearBtn = new Button(bundle.getString("remove"));
            slideClearBtn = new Button(bundle.getString("remove"));
            videoClearBtn.setImmediate(true);
            slideClearBtn.setImmediate(true);
            videoClearBtn.addClickListener(new Button.ClickListener() {
                @Override
                public void buttonClick(Button.ClickEvent event) {
                    CourseMaterialEntity entity = (CourseMaterialEntity) binder.getItemDataSource().getBean();
                    if (entity != null) {
                        entity.setVideoURL(null);
                        videoURL.setVisible(false);
                    }
                    event.getButton().setVisible(false);
                }
            });
            slideClearBtn.addClickListener(new Button.ClickListener() {
                @Override
                public void buttonClick(Button.ClickEvent event) {
                    CourseMaterialEntity entity = (CourseMaterialEntity) binder.getItemDataSource().getBean();
                    if (entity != null) {
                        entity.setSlideURL(null);
                        slideURL.setVisible(false);
                    }
                    event.getButton().setVisible(false);
                }
            });

            hLayout = new HorizontalLayout();
            hLayout.setMargin(new MarginInfo(false, true, false, false));
            hLayout.addComponent(videoURL);
            hLayout.addComponent(videoClearBtn);
            formGeneral.addComponent(hLayout);
            hLayout = new HorizontalLayout();
            hLayout.setMargin(new MarginInfo(false, true, false, false));
            hLayout.addComponent(slideURL);
            hLayout.addComponent(slideClearBtn);
            formGeneral.addComponent(hLayout);
            formGeneral.addComponent(uploadFile);

            formGeneral.addComponent(getSubmitButton());

            formGeneral.setImmediate(true);
            tabSheet.addTab(formGeneral, bundle.getString("tab_general"));

            formShortDescription = new FormLayout();
            formShortDescription.setMargin(new MarginInfo(false, true, true, true));
            formShortDescription.setImmediate(true);
            CKEditorTextField shortDescription = getWYSIWYGEditor();
            formShortDescription.addComponent(shortDescription);
            formShortDescription.addComponent(getSubmitButton());
            tabSheet.addTab(formShortDescription, bundle.getString("tab_shortdescription"));

            CKEditorTextField description = getWYSIWYGEditor();
            formDescription = new FormLayout();
            formDescription.addComponent(description);
            formDescription.addComponent(getSubmitButton());
            formDescription.setImmediate(true);
            formDescription.setMargin(new MarginInfo(false, true, true, true));
            tabSheet.addTab(formDescription, bundle.getString("tab_fulldescription"));

            binder.bind(title, "title");
            binder.bind(startDate, "startDate");
            binder.bind(finishDate, "dueDate");
            binder.bind(shortDescription, "shortDescription");
            binder.bind(description, "text");
            binder.bind(sectionTitleBox, "sectionTitle");

            containerLayout.setSizeFull();
            containerLayout.addComponent(tabSheet);
            containerLayout.setImmediate(true);
        }

        private void initSectionTitleValues() {
            try {
                CourseMaterialEntity entity = (CourseMaterialEntity) binder.getItemDataSource().getBean();
                List<String> sectionTitles = material.getSectionTitles(entity.getCourse().getId());
                for (String sectionTitle : sectionTitles) {
                    sectionTitleBox.addItem(sectionTitle);
                }
            } catch (ServiceException e) {
                logger.error("initSectionTitleValues() Material exception", e);
            }
        }

        /**
         * Return instantiated button with initiated listener used for Form layout
         *
         * @return Button object
         */
        private Button getSubmitButton() {
            Button btn = new Button(bundle.getString("save"));
            btn.addClickListener(submitListener);
            return btn;
        }

        /**
         * @param entity
         */
        private void setEntity(CourseMaterialEntity entity) {
            if (entity == null) {
                logger.info("setEntity() entity object is null");
                throw new IllegalArgumentException("Entity object can't be null");
            }
            binder.setItemDataSource(entity);
            initSectionTitleValues();
            if (entity.getVideoURL() != null &&
                    !entity.getVideoURL().isEmpty()) {
                videoURL.setCaption(bundle.getString("videoURL"));
                videoURL.setResource(new ExternalResource(entity.getVideoURL()));
                videoURL.setVisible(true);
                videoClearBtn.setVisible(true);
            } else {
                videoURL.setVisible(false);
                videoClearBtn.setVisible(false);
            }

            if (entity.getSlideURL() != null &&
                    !entity.getSlideURL().isEmpty()) {
                slideURL.setCaption(bundle.getString("slideURL"));
                slideURL.setResource(new ExternalResource(entity.getSlideURL()));
                slideURL.setVisible(true);
                slideClearBtn.setVisible(true);
            } else {
                slideURL.setVisible(false);
                slideClearBtn.setVisible(false);
            }
        }

        /**
         * Wrapper of Google drive file uploader.
         *
         * @param fileName File to be uploaded
         * @param mimeType MIME Type of the file.
         * @return Map with file info in case of success or null.
         */
        private Map<String, String> sendFileToGDrive(String fileName, String mimeType) {
            logger.debug("sendFileToGDrive() - started.");

            Map<String, String> fileInfo = null;
            try {
                fileInfo = GoogleDriveUploader.uploadFileToDrive(fileName, mimeType);
            } catch (GDriveUploadException e) {
                logger.error("sendFileToGDrive() Can't upload file to GDrive. Error: {}, \nTrace:\n {}", e.getMessage(),
                        Arrays.deepToString(e.getStackTrace()));
            } catch (GDriveFilePermissionException e) {
                logger.error("sendFileToGDrive() Can't set permissions to uploaded file. Error: {}, \nTrace:\n {}", e.getMessage(),
                        Arrays.deepToString(e.getStackTrace()));
            } catch (GoogleTransportException e) {
                logger.error("sendFileToGDrive() Can't setup connection. Error: {}, \nTrace:\n {}", e.getMessage(),
                        Arrays.deepToString(e.getStackTrace()));
            } catch (GoogleAuthException e) {
                logger.error("sendFileToGDrive() Can't authorize on service. Error: {}, \nTrace:\n {}", e.getMessage(),
                        Arrays.deepToString(e.getStackTrace()));
            }

            if (fileInfo == null) {
                Notification.show("Не могу загрузить файл на Google Drive. Попробуйте позже или добавьте его вручную",
                        Notification.Type.ERROR_MESSAGE);
            }
            if (fileInfo != null && !fileInfo.isEmpty()) {
                Notification.show("Файл загружен на Google Drive.", Notification.Type.TRAY_NOTIFICATION);
            }

            logger.debug("sendFileToGDrive() - finished.");
            return fileInfo;
        }

        /**
         * Class that implements ClickListener for submit button
         */
        private class SubmitBtnListener implements Button.ClickListener {
            @Override
            public void buttonClick(Button.ClickEvent event) {
                try {
                    binder.commit();
                    material.persist((CourseMaterialEntity) binder.getItemDataSource().getBean());
                    Notification.show(bundle.getString("saved"));
                    hsplit.setSecondComponent(emptyLayout);
                    resetTreeContent();
                } catch (FieldGroup.CommitException e) {
                    logger.error("BeanBinder commit exception", e);
                } catch (ServiceValidationException | ServiceException e) {
                    logger.error("Can't persist material", e);
                }
            }
        }

        /**
         * Class that implements interfaces for Upload component
         */
        private class UploadListener implements Upload.Receiver, Upload.SucceededListener, Upload.FailedListener {

            @Override
            public void uploadFailed(Upload.FailedEvent event) {
                logger.debug("uploadFailed() - started.");
                Notification.show("Загрузка файла не удалась.", Notification.Type.ERROR_MESSAGE);
                logger.debug("uploadFailed() - finished.");
            }

            @Override
            public OutputStream receiveUpload(String filename, String mimeType) {
                logger.debug("receiveUpload() - started");
                if (!StandardGoogleApiVariables.isUploadDirExists(true)) {
                    logger.error("Upload catalog does not exists and can not be created. Exiting");
                    Notification.show("Каталог для загрузок " + StandardGoogleApiVariables.UPLOAD_DIR + " отсутствует " +
                            "и не может быть создан. Создайте его вручную и повторите попытку", Notification.Type.ERROR_MESSAGE);
                    return null;
                }

                FileOutputStream out = null;
                File file = new File(StandardGoogleApiVariables.UPLOAD_DIR + filename);
                logger.debug("receiveUpload() Got upload file name: {}. Trying to upload...", file);

                try {
                    out = new FileOutputStream(file);
                    logger.debug("receiveUpload() File stream created.");
                } catch (FileNotFoundException e) {
                    logger.error("Can't open file stream. Error message: {}, \nTrace:\n {}", e.getMessage(),
                            Arrays.deepToString(e.getStackTrace()));
                    logger.debug("receiveUpload() - finished with FileNotFoundException. Null will be returned.");
                    return null;
                }

                logger.debug("receiveUpload() - finished.");
                return out;
            }

            @Override
            public void uploadSucceeded(Upload.SucceededEvent event) {
                Map<String, String> result;
                logger.debug("uploadSucceeded() - started");
                Notification.show("Файл успешно загружен на сервер.", Notification.Type.TRAY_NOTIFICATION);
                logger.debug("Trying to upload file at GoogleDrive.");
                CourseMaterialEntity material = ((CourseMaterialEntity) binder.getItemDataSource().getBean());

                if (event.getMIMEType().startsWith("video")) {
                    result = sendFileToYouTube(event.getFilename(), event.getFilename(), "");
                    if (result != null) {
                        material.setVideoURL("https://www.youtube-nocookie.com/embed/" + result.get("id"));
                        videoURL.setCaption(bundle.getString("videoURL"));
                        videoURL.setResource(new ExternalResource(material.getVideoURL()));
                        videoClearBtn.setVisible(true);
                    }
                } else {
                    result = sendFileToGDrive(event.getFilename(), event.getMIMEType());
                    if (result != null) {
                        material.setSlideURL(result.get("alternateLink"));
                        slideURL.setCaption(bundle.getString("slideURL"));
                        slideURL.setResource(new ExternalResource(material.getSlideURL()));
                        slideClearBtn.setVisible(true);
                    }
                }
                logger.debug("uploadSucceeded() - finished.");
            }

            /**
             * Wrapper of YouTubeUploader.
             *
             * @param fileName File name.
             * @param title    Video title. For example, you can use material title.
             * @param desc     Video description. You can use material short description.
             * @return Map with video info in case of success or null.
             */
            private Map<String, String> sendFileToYouTube(String fileName, String title, String desc) {
                logger.debug("sendFileToYouTube() - started.");

                Map<String, String> videoInfo = null;

                try {
                    videoInfo = YouTubeUploader.uploadFileToYouTube(fileName, title, desc);
                } catch (GoogleTransportException e) {
                    logger.error("sendFileToYouTube() Can't setup connection. Error: {}, \nTrace:\n {}", e.getMessage(),
                            Arrays.deepToString(e.getStackTrace()));
                } catch (GoogleAuthException e) {
                    logger.error("sendFileToYouTube() Can't authorize on service. Error: {}, \nTrace:\n {}", e.getMessage(),
                            Arrays.deepToString(e.getStackTrace()));
                } catch (YouTubeUploadException e) {
                    logger.error("sendFileToYouTube() Can't upload file to YouTube. Error: {}, \nTrace:\n {}", e.getMessage(),
                            Arrays.deepToString(e.getStackTrace()));
                }

                if (videoInfo == null) {
                    Notification.show("Не могу загрузить файл на YouTube. Попробуйте позже или добавьте его вручную",
                            Notification.Type.ERROR_MESSAGE);
                }
                if (videoInfo != null && !videoInfo.isEmpty()) {
                    Notification.show("Файл загружен на YouTube", Notification.Type.TRAY_NOTIFICATION);
                }

                logger.debug("sendFileToYouTube() - finished.");
                return videoInfo;
            }
        }

    }

    private class HomeWorkTabsheetLayout {
        private final BeanFieldGroup binder;
        private TabSheet tabSheet;
        private FormLayout formGeneral;
        private SubmitBtnListener submitListener = new SubmitBtnListener();
        private VerticalLayout containerLayout = new VerticalLayout();

        private HomeWorkTabsheetLayout() {
            tabSheet = new TabSheet();
            tabSheet.setHeight(100, Unit.PERCENTAGE);
            tabSheet.setImmediate(true);
            formGeneral = new FormLayout();
            CKEditorTextField description = getWYSIWYGEditor();

            binder = new BeanFieldGroup(HomeWorkEntity.class);
            TextField title = new TextField(bundle.getString("title"));
            DateField startDate = new DateField(bundle.getString("start_date"));
            DateField finishDate = new DateField(bundle.getString("due_date"));
            HorizontalLayout hLayout = new HorizontalLayout();
            hLayout.addComponent(startDate);
            hLayout.addComponent(finishDate);
            formGeneral.addComponent(title);
            formGeneral.addComponent(hLayout);
            formGeneral.setMargin(new MarginInfo(false, true, true, true));
            formGeneral.addComponent(description);
            formGeneral.addComponent(getSubmitButton());
            formGeneral.setImmediate(true);
            tabSheet.addTab(formGeneral, bundle.getString("tab_general"));
            binder.bind(title, "title");
            binder.bind(startDate, "startDate");
            binder.bind(finishDate, "dueDate");
            binder.bind(description, "description");

            containerLayout.setSizeFull();
            containerLayout.addComponent(tabSheet);
            containerLayout.setImmediate(true);
        }

        private Button getSubmitButton() {
            Button btn = new Button(bundle.getString("save"));
            btn.addClickListener(submitListener);
            return btn;
        }

        private void setEntity(HomeWorkEntity entity) {
            if (entity == null) {
                logger.info("setEntity() entity object is null");
                throw new IllegalArgumentException("Entity object can't be null");
            }
            binder.setItemDataSource(entity);
        }

        /**
         * Class that implements ClickListener for submit button
         */
        private class SubmitBtnListener implements Button.ClickListener {
            @Override
            public void buttonClick(Button.ClickEvent event) {
                try {
                    binder.commit();
                    homework.persist((HomeWorkEntity) binder.getItemDataSource().getBean());
                    Notification.show(bundle.getString("saved"), Notification.Type.TRAY_NOTIFICATION);
                    hsplit.setSecondComponent(emptyLayout);
                    resetTreeContent();
                } catch (FieldGroup.CommitException e) {
                    logger.error("BeanBinder commit exception", e);
                } catch (ServiceValidationException | ServiceException e) {
                    logger.error("Can't persist material", e);
                }
            }
        }
    }
}
